//Grading Template

/* ID         :  1632842
   Assignment :  RandomWriter
   Class      :  CSIS 310 Data Structures
   Date       :  7 December 2011

Programming Assesment Standards

PROGRAM CORRECTNESS
   a) Functions correctly (e.g. output is correct)       (40)________
   b) Correct handling of other exceptional conditions   (10)________
      (bad input, missing files, etc.)
PROGRAM DESIGN
   c) Appropriate choice & use of data structures        (10)________
   d) Subjective "elegance" (e.g., flow, design)         (20)________
PROGRAM READABILITY AND DOCUMENTATION
   e) Header comments are complete & clear                (5)________
   f) Internal comments are used when appropriate        (15)________

TOTAL                                                   (100)________

******************************************************************/
/**
 * RandomWriter is a standalone program which takes a text file as input
 * and outputs to console a version of that text file in a style reminiscent
 * of the original author.
 * 
 * @author 1632842
 * @version 1
 * @see HashMap, Scanner, BufferedReader, PrintWriter, InputStreamReader,
 * @see FileInputStream, Charset
 */
import java.util.*;
import java.io.*;
import java.nio.charset.Charset;


public class RandomWriter
{

	/**
	 * Random writer takes for input a seed length, a number of characters to
	 * send to System.out, and one or more text files as input.  If no text
	 * files are offered, the program will ask for input from System.in. 
	 * @param args an integer k that is the seed, an integer L for the length
	 * of characters to be output, and (optionally) one or more text file
	 * locations.
	 */
	public static void main(String[] args)
	{
		int seedLength = 0;
		int writeLength = 0;
		ArrayList<String> fileHolder = new ArrayList<String>();
		StringBuffer seedString = new StringBuffer();
		File textFileToScan = null;
		HashMap<String, ArrayList<Character>> dictionary =
								new HashMap<String, ArrayList<Character>>(100);
		BufferedReader fileCharacterReader = null;
		int characterNumber = 0;
		ArrayList<Character> hashCharArray = null;
		char c;
		Random randomKey = new Random();
		ArrayList<String> randomizerKeys = new ArrayList<String>();
		String grabSeedKey = "";
		String charString = "";
		StringBuffer keyRunner = new StringBuffer();
		int randCharNum = 0;
		int offset = 0;
		Scanner readCommandLine;
		String cmdLineInput = "";
		String[] splitCmdLineInput = null;
		StringBuffer cmdLineFile = new StringBuffer();
		PrintWriter writeFile = null;
		File newFile = null;
		StringBuffer scannerInput = new StringBuffer();
		
		
		// first, check the amount of arguments given.  if there is only one arg
		// given, or if the command is passed with -h or --help, usage is
		// displayed
		System.out.println(args.length);
		
		//Case 1.  Too few args or -h flag is passed.
		if(args.length < 2 || args[0].equals("-h") || args[0].equals("--help"))
		{
			System.err.println("Useage: Seedlength<int> OutputLength<int>" +
					" inputfile<file>.  There may be more than one file." +
					"  If no file is given, the text to be randomized" +
					" will be input manually.");
			System.exit(1);
		} //if args.length < 2
	
		// parse the integers - checkPositivity will throw an exception if
		// the parsing fails.
		seedLength = checkPositivity(args[0]);
		writeLength = checkPositivity(args[1]);
		
		//if any of the lengths are negative, inform and exit.
		if(seedLength < 0 || writeLength < 0)
		{
			System.err.println("Seed number or amount to write is negative.");
			System.exit(1);
		} // if seedLengths or write lengths are negative.		
		
		//Case 2: no files are given; open up the scanner and get some input.
		if(args.length == 2)
		{
			//create the scanner
			readCommandLine = new Scanner(System.in);
			
			// while we still have console input to read
			while(readCommandLine.hasNext())
			{
				//append to the stringbuffer scannerInput
				scannerInput.append(readCommandLine.next());
			} //while
			
			// convert the StringBuffer to a String...
			cmdLineInput = scannerInput.toString();
			
			// ... then split it on the whitespace
			splitCmdLineInput = cmdLineInput.split("\\s+");
			
			// loop to analyze the split chunks.
			for(int i = 0; i < splitCmdLineInput.length; i++)
			{
				// create a file using the first chunk.
				textFileToScan = new File(splitCmdLineInput[i]);
				
				// that was a file?  great!  add it to the files to analyze.
				if(textFileToScan.exists())
				{
					
					fileHolder.add(splitCmdLineInput[i]);
					
				} //if
				else
				{
					// otherwise its just text.  append it to a stringbuffer
					// for later.
					cmdLineFile.append(splitCmdLineInput[i]);
					
				} //else
				
			}
			// There are things in that stringbuffer we would like to convert
			// into an actual file for the program to analyze.
			if(cmdLineFile.length() > 0)
			{
				// make a new file called output.txt
				newFile = new File("output.txt");
				
				if(!newFile.exists())
				{
					try
					{
						// It doesn't exist, so make it on the directory.
						newFile.createNewFile();
					} 
					catch (IOException e) //didn't work, exit gracefully
					{
						System.err.println("Saving input to a file failed.  " +
								"Please try again.");
						System.exit(1);
					}
				}
				
				try
				{
					// open a new printwriter
					writeFile = new PrintWriter("output.txt");
					//write all that text we captured to the output.file
					writeFile.print(cmdLineFile.toString());
					// close the file
					writeFile.close();
					// add it to the files to parse.
					fileHolder.add("output.txt");
				} // try
				catch (FileNotFoundException e)
				{
					// this is here to satisfy opening a printwriter on a 
					// possibly non-existent file.  We just created it, so 
					// somewhat moot.
					 System.err.println("Saving input to a file failed.  " +
								"Please try again.");
						System.exit(1);
				} //catch
				
			} //if
			
		} // Case 2
		
		// Case 3.  One or more files given.
		if(args.length > 2 || fileHolder.size() > 0)
		{
			
			for(int i = 2; i < args.length; i++)
			{
				fileHolder.add(args[i]);
			} //building the array of filenames to iterate over
			
			for(int i = 0; i < fileHolder.size(); i++)
			{
				// get a new file element
				textFileToScan = new File(fileHolder.get(i));
				
				// reset the stringbuffer
				seedString = new StringBuffer();
				
				try
				{
					// open a buffered reader on the file
					fileCharacterReader = new BufferedReader
						(new InputStreamReader(new FileInputStream
								(textFileToScan), Charset.forName("US-ASCII")));
					try
					{
						// while there are still characters to get out of the
						// file...
						while((characterNumber = fileCharacterReader.read())
																		!= -1)
						{
							// ...get the character.
							c = (char) characterNumber;
							
							//append it to the seedString
							seedString.append(c);
							
							// I grab the k length seed and the trailing
							// character to add.
							if(seedString.length() == seedLength + 1)
							{
								
								if(dictionary.containsKey(seedString.subSequence
										(0, seedLength).toString()))
								{
									dictionary.get(seedString.subSequence
											(0, seedLength).toString())
											.add(seedString.charAt(seedLength));
								} // string is in the hashmap.  add the letter
								  //to the arraylist value.
								else
								{
									hashCharArray = new ArrayList<Character>();
									hashCharArray.add(c);
									dictionary.put(seedString.subSequence
											(0, seedLength).toString(),
												hashCharArray);
									randomizerKeys.add(seedString.subSequence
											(0, seedLength).toString());
								} // string is not in the hashmap, so create a
								  // new arraylist, add the character to it,
								  // then add that to the hashmap.
								
								// get rid of the first character in the
								// seedString in prep for the next to be added.
								seedString.deleteCharAt(0);
							}
						} //while we are not at EOF
						
					}  // try block to attempt character read in
					catch (IOException e)
					{
						System.err.println("A error occured reading from" +
								" the file.  Moving on...");
					} // catch read errors.  print some info and move on.
					
				} 
				catch (FileNotFoundException e)
				{
					System.err.println("File Not Found.  Moving on...");
				} // the file was not found.  print some info and move on.
				
			} // loop that attempts learning while there are files to learn from.
								
		} // if args.length > 2
		

		//pick a random seed from the seed array.
		grabSeedKey = randomizerKeys.get
								(randomKey.nextInt(randomizerKeys.size()));

		//put it in a stringbuffer so we can manipulate it.
		keyRunner.append(grabSeedKey);
		
		// while we still have letters to print out.
		while(offset < writeLength)
		{
			if(dictionary.containsKey(keyRunner.toString()))
			{
				//get the size of arraylist at that key
				randCharNum = dictionary.get(keyRunner.toString()).size();
				//get one character value at random from that arraylist
				charString = Character.toString(dictionary.get(keyRunner.toString())
										.get(randomKey.nextInt(randCharNum)));
				// print it
				System.out.print(charString);
				// delete the head
				keyRunner.deleteCharAt(0);
				// append the printed character
				keyRunner.append(charString);
			} // if
		
			// advance the offset.
			offset++;
			
		}  //while

	} // main
	
	/**
	 *  checkPositivity parses the seed length and amount to write integers.
	 *  It will return negative values, which will need to be filtered for if
	 *  negatives are not allowed.
	 * @param String s of the string value to be parsed into an integer.
	 * @return the string value parsed into an integer.
	 * @throws NumberFormatException if parsing the string fails.
	 */
	private static int checkPositivity(String s) throws NumberFormatException
	{
		int parsedInteger = 0;
		// pretty straightforward.
		try
		{
			parsedInteger = Integer.parseInt(s);
		}
		catch(NumberFormatException e)
		{
			throw new NumberFormatException(s + " is not a valid integer.");
		}
		
		return parsedInteger;
	}
	
}
